﻿using CommunityToolkit.Mvvm.Input;
using HandyControl.Controls;
using ImageHandler.Models;
using ImageMagick.Drawing;
using Microsoft.Win32;
using OpenCvSharp;
using OpenCvSharp.XPhoto;
using System;
using System.Drawing.Drawing2D;
using System.IO;
using System.Reflection.Metadata;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;

namespace ImageHandler.ViewModels
{
    /// <summary>
    /// https://www.cnblogs.com/qq2806933146xiaobai/p/18294219
    /// </summary>
    public class E000_StartSimpleViewModel : PartViewModel
    {
        private Mat _srcMat;
        public E000_StartSimpleViewModel()
        {
            Header = "基础示例";
        }

        public override void Open(string fileName)
        {
            base.Open(fileName);
            _srcMat = Cv2.ImRead(fileName, ImreadModes.Color);
            if (_srcMat.Empty())
            {
                OperateLogAction?.Invoke("无法读取图像！");
            }
        }

        public override void Operate(string _fileName, string action)
        {
            if(_srcMat == null || _srcMat.Empty())
            {
                OperateLogAction?.Invoke("请先选择图像！");
                return;
            }

            if (action == "2Gray")
            {
                // 灰度图像
                Mat gray = new Mat();
                Cv2.CvtColor(_srcMat, gray, ColorConversionCodes.BGR2GRAY);
                AddMatToResult(gray, "灰度图像");
            }
            else if (action == "Mean2Threshold")
            {
                Mat gray = new Mat();
                Cv2.CvtColor(_srcMat, gray, ColorConversionCodes.BGR2GRAY);  // 灰度图

                // 二值化 - 阈值为灰度图平均值
                Scalar scalar = Cv2.Mean(gray);                                      // 计算灰度图平均值
                Cv2.Threshold(gray, gray, scalar.Val0, 255, ThresholdTypes.Binary);  // 二值化
                AddMatToResult(gray, "二值化-灰度图平均值");
            }
            else if (action == "AdaptiveThreshold")
            {
                Mat gray = new Mat();
                Cv2.CvtColor(_srcMat, gray, ColorConversionCodes.BGR2GRAY);  // 灰度图

                //自动计算最佳阈值
                Mat binaryMat = new Mat();
                Cv2.Threshold(gray, binaryMat, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
                AddMatToResult(binaryMat, "二值化-自动计算最佳阈值");
            }
            else if (action == "Blur")
            {
                var dstMet = new Mat();
                var ksize = new Size(3, 3); // 卷积核大小, 滤波模版Kernel的尺寸;必须是正奇数,值越大越模糊
                var anchor = new OpenCvSharp.Point(-1, -1); // 锚点
                var borderType = BorderTypes.Default; // 边界类型
                Cv2.Blur(_srcMat, dstMet, ksize, anchor, borderType);
                AddMatToResult(dstMet, "均值滤波");
            }
            else if (action == "GaussianBlur")
            {
                var dstMet = new Mat();
                var ksize = new Size(3, 3); // 卷积核大小, 滤波模版Kernel的尺寸;必须是正奇数,值越大越模糊
                var sigmaX = 5; // X轴方向的标准差,值越大越模糊
                var sigmaY = 5; // Y轴方向的标准差,值越大越模糊
                var borderType = BorderTypes.Default; // 边界类型
                Cv2.GaussianBlur(_srcMat, dstMet, ksize, sigmaX, sigmaY, borderType);
                AddMatToResult(dstMet, "高斯滤波");
            }
            else if (action == "MedianBlur")
            {
                var dstMet = new Mat();
                var ksize = 5; // 卷积核大小, 滤波模版Kernel的尺寸;必须是正奇数,值越大越模糊
                Cv2.MedianBlur(_srcMat, dstMet, ksize);
                AddMatToResult(dstMet, "中值滤波");
            }
            else if (action == "BilateralFilter")
            {
                var dstMet = new Mat();
                var d = 9; // 滤波器内核的大小。较大的值可以捕获更多的像素，从而平滑图像，但也会降低边缘保持能力。通常这个值是奇数。
                var sigmaColor = 25; // 颜色空间的标准差, 它定义了在什么范围内颜色被认为是相似的。较大的值会使颜色更加平滑。
                var sigmaSpace = 25; // 坐标空的标准差。它定义了在什么范围内像素被认为是邻近的。较大的值会使空间距离较远的像素也相互影响，从而平滑图像
                Cv2.BilateralFilter(_srcMat, dstMet, d, sigmaColor, sigmaSpace);
                AddMatToResult(dstMet, "双边滤波");
            }
            else if (action == "PyrDown")
            {
                //高斯金字塔（向下取样；大->小）, 分辨率变小而模糊
                var dstMet = new Mat();
                var dstSize = new Size(_srcMat.Cols / 2, _srcMat.Rows / 2); // 输出图像的大小
                var borderType = BorderTypes.Default; // 外部像素的边界模式
                Cv2.PyrDown(_srcMat, dstMet, dstSize, borderType);
                AddMatToResult(dstMet, "高斯金字塔-向下取样");
            }
            else if (action == "PyrUp")
            {
                //拉普拉斯金字塔（向上取样；小->大）,  放大失真而模糊
                var dstMet = new Mat();
                var dstSize = new Size(_srcMat.Cols * 2, _srcMat.Rows * 2); // 输出图像的大小
                var borderType = BorderTypes.Default; // 外部像素的边界模式
                Cv2.PyrUp(_srcMat, dstMet, dstSize, borderType);
                AddMatToResult(dstMet, "拉普拉斯金字塔-向上取样");
            }
            else if (action == "Erode")
            {
                // 腐蚀操作
                var dstMet = new Mat();
                var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 5)); // 结构元素
                var anchor = new OpenCvSharp.Point(-1, -1); // 锚点
                var iterations = 1; // 腐蚀次数
                Cv2.Erode(_srcMat, dstMet, kernel, anchor, iterations);
                AddMatToResult(dstMet, "腐蚀操作");
            }
            else if (action == "Dilate")
            {
                // 膨胀操作
                var dstMet = new Mat();
                var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 5)); // 结构元素
                var anchor = new OpenCvSharp.Point(-1, -1); // 锚点
                var iterations = 1; // 膨胀次数
                Cv2.Dilate(_srcMat, dstMet, kernel, anchor, iterations);
                AddMatToResult(dstMet, "膨胀操作");
            }
            else if (action == "MorphologyExClose")
            {
                // 形态学操作
                var dstMet = new Mat();
                var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 5)); // 结构元素
                var anchor = new OpenCvSharp.Point(-1, -1); // 锚点
                var iterations = 1; // 膨胀次数
                Cv2.MorphologyEx(_srcMat, dstMet, MorphTypes.Close, kernel, anchor, iterations);
                AddMatToResult(dstMet, "形态学操作");
            }
            else if (action == "MorphologyExOpen")
            {
                // 形态学操作
                var dstMet = new Mat();
                var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 5)); // 结构元素
                var anchor = new OpenCvSharp.Point(-1, -1); // 锚点
                var iterations = 1; // 膨胀次数
                Cv2.MorphologyEx(_srcMat, dstMet, MorphTypes.Open, kernel, anchor, iterations);
                AddMatToResult(dstMet, "形态学操作");
            }
            else if (action == "Canny")
            {
                // 边缘检测
                var dstMet = new Mat();
                var threshold1 = 100; // 较低的阈值
                var threshold2 = 200; // 较高的阈值
                Cv2.Canny(_srcMat, dstMet, threshold1, threshold2);
                AddMatToResult(dstMet, "Canny边缘检测");
            }
            else if (action == "ChangeLight")
            {
                // 转Hsv图片
                Mat hsv = new Mat();
                Cv2.CvtColor(_srcMat, hsv, ColorConversionCodes.BGR2HSV);

                // 取V通道并修改值
                Cv2.Split(hsv, out Mat[] mats);
                int modifyValue = 128;
                Mat mat2 = new Mat();
                Cv2.Add(mats[2], new Scalar(modifyValue), mat2);
                mats[2] = mat2;

                // 合并
                Mat dstMet = new Mat();
                Cv2.Merge(mats, dstMet);
                AddMatToResult(dstMet, "修改亮度");
            }
            else if (action == "GRAY_Linear")
            {
                //灰度图的线性变换
                Mat grayMat = new Mat();
                Cv2.CvtColor(_srcMat, grayMat, ColorConversionCodes.BGR2GRAY);  // 灰度图

                Mat dstMat = new Mat();
                grayMat.CopyTo(dstMat);

                double a = 1.2; // 线性变换系数
                double b = 40; // 线性变换偏移量
                for (int w = 0; w < grayMat.Width; w++)
                {
                    for (int h = 0; h < grayMat.Height; h++)
                    {
                        byte color = grayMat.At<byte>(h, w);
                        int colorNew = Convert.ToInt32(a * color + b);
                        colorNew = colorNew > 255 ? 255 : colorNew;

                        dstMat.At<byte>(h, w) = Convert.ToByte(colorNew);
                    }
                }
                AddMatToResult(dstMat, "线性变换");
            }
            else if (action == "GRAY_DividedLinear")
            {
                //灰度图的分段线性变换
                Mat grayMat = new Mat();
                Cv2.CvtColor(_srcMat, grayMat, ColorConversionCodes.BGR2GRAY);  // 灰度图
                Mat dstMat = Mat.Zeros(grayMat.Size(), grayMat.Type());

                float fStart = 70; // 分段区间起点
                float fEnd = 150; // 分段区间终点
                float fsOut = 100; // 映射区间起点
                float feOut = 200; // 映射区间终点

                // 计算直线参数
                // L1
                float fk1 = fsOut / fStart;
                // L2
                float fk2 = (feOut - fsOut) / (fEnd - fStart);
                float fc2 = fsOut - fk2 * fStart;
                // L3
                float fk3 = (255 - feOut) / (255 - fEnd);
                float fc3 = 255 - fk3 * 255;

                //建立查询表
                byte[] lookupTable = new byte[256];
                for (int i = 0; i < 256; i++)
                {
                    if (i < fStart)
                    {
                        lookupTable[i] = (byte)(fk1 * i);
                    }
                    else if (i <= fEnd)
                    {
                        lookupTable[i] = (byte)(fk2 * i + fc2);
                    }
                    else
                    {
                        lookupTable[i] = (byte)(fk3 * i + fc3);
                    }
                }

                // 灰度映射
                for (int w = 0; w < grayMat.Width; w++)
                {
                    for (int h = 0; h < grayMat.Height; h++)
                    {
                        byte color = grayMat.At<byte>(h, w);
                        dstMat.At<byte>(h, w) = lookupTable[color];
                    }
                }

                AddMatToResult(dstMat, "分段线性变换");
            }
            else if (action == "EqualizeHist")
            {
                // 直方图均衡化
                Mat grayMat = new Mat();
                Cv2.CvtColor(_srcMat, grayMat, ColorConversionCodes.BGR2GRAY);  // 灰度图
                Mat dstMat = new Mat();
                Cv2.EqualizeHist(grayMat, dstMat);
                AddMatToResult(dstMat, "直方图均衡化");
            }
            else if (action == "WhiteBalance_Gamma")
            {
                //白平衡矫正 -灰度幂律变换（伽马变换）
                //对过曝和过暗的图片进行矫正
                Mat grayMat = new Mat();
                Cv2.CvtColor(_srcMat, grayMat, ColorConversionCodes.BGR2GRAY);  // 灰度图
                Mat dstMat = new Mat();
                for(int w=0; w < grayMat.Width; w++)
                {
                    for(int h=0; h < grayMat.Height; h++)
                    {
                        byte color = grayMat.At<byte>(h, w);
                        dstMat.At<byte>(h, w) = Convert.ToByte(Math.Pow(color, 0.5));
                    }
                }
                Cv2.Normalize(dstMat, dstMat, 0, 255, NormTypes.MinMax);
                AddMatToResult(dstMat, "白平衡矫正-灰度幂律变换");
            }
            else if (action == "WhiteBalanc_GrayworldWB")
            {
                //白平衡矫正 -灰度世界(GrayworldWB)
                Mat dstMat = new Mat();
                WhiteBalancer wb = CvXPhoto.CreateGrayworldWB();
                wb.BalanceWhite(_srcMat, dstMat);
                AddMatToResult(dstMat, "白平衡矫正-灰度世界");
            }
            else if (action == "WhiteBalanc_SimpleWB")
            {
                //白平衡矫正 - 完美反射
                Mat dstMat = new Mat();
                WhiteBalancer wb = CvXPhoto.CreateSimpleWB();
                wb.BalanceWhite(_srcMat, dstMat);
                AddMatToResult(dstMat, "白平衡矫正-完美反射");
            }
            else if (action == "WhiteBalanc_LearningBasedWB")
            {
                //白平衡矫正 --基于学习的(LearningBasedWB)
                Mat dstMat = new Mat();
                string model = "model/LearningBasedWB";  // 模型路径
                WhiteBalancer wb = CvXPhoto.CreateLearningBasedWB(model);
                wb.BalanceWhite(_srcMat, dstMat);
                AddMatToResult(dstMat, "白平衡矫正-基于学习");
            }

            else if (action == "Demo")
            {
                Demo();
            }

        }

        private void Demo()
        {
            int width = 640;
            int height = 480;

            string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test.png");
            var mat = Cv2.ImRead(imagePath, ImreadModes.Color);

            // 创建一个空的图像
            Mat mat0 = new Mat();
            Mat mat1 = new Mat(width, height, MatType.CV_8UC1);  // 宽,高,样式
            // 创建全0矩阵
            Mat mat2 = Mat.Zeros(mat1.Size(), mat1.Type());
            // 创建全1矩阵
            Mat kernel = Mat.Ones(10, 10, MatType.CV_8UC1);
            // 创建全255矩阵
            Mat mat3 = Mat.Ones(mat1.Size(), mat1.Type()) * 255;

            //复制
            Mat dstMat = new Mat(); 
            mat3.CopyTo(dstMat);  // 图像mat3深层复制给图像dstMat

            //调整图片宽高, 缩放图片
            Mat resizeMat = new Mat();
            Cv2.Resize(mat, resizeMat, new OpenCvSharp.Size(width, height));

            //裁剪图片
            Rect rect = new Rect(0, 0, 200, 200);
            Mat cropMat = new Mat(mat, rect);

            //压缩图片
            int multiple = 2; // 压缩倍数
            Mat compressMat = new Mat();
            Cv2.Resize(mat, compressMat, new OpenCvSharp.Size(mat.Cols / multiple, mat.Rows / multiple));

            //拼接图片
            bool isV = true; // true=上下拼接，false=左右拼接
            var mats = new List<Mat>() { mat, mat1, mat2};
            Mat concatMat = new Mat();
            if (isV)
            {
                Cv2.VConcat(mats, concatMat);
            }
            else
            {
                Cv2.HConcat(mats, concatMat);
            }

            // 拆分通道
            Cv2.Split(mat, out Mat[] outMats);
            // 合并通道
            Mat mergeMat = new Mat();
            Cv2.Merge(outMats, mergeMat);
            mergeMat.ConvertTo(mergeMat, MatType.CV_8U); // 转换类型

            //旋转图片
            var rotate = RotateFlags.Rotate90Clockwise; // 旋转方向
            Mat rotateMat = new Mat();
            Cv2.Rotate(mat, rotateMat, rotate);
            // 任意角度旋转
            var angle = 45; // 旋转角度
            var center = new OpenCvSharp.Point(mat.Cols / 2, mat.Rows / 2); // 旋转中心
            var input = Cv2.GetRotationMatrix2D(center, angle, 1.0); // 旋转矩阵
            var dsize = new OpenCvSharp.Size(mat.Cols, mat.Rows); // 输出图像大小
            var rotateMat2 = WarpAffine(mat, input, dsize, InterpolationFlags.Linear, BorderTypes.Default, new Scalar(0, 0, 0)); // 旋转图像

            // 图像平移
            var tx = 100; // 水平平移
            var ty = 50; // 垂直平移
            var translation = new Mat(2, 3, MatType.CV_64F, new double[] { 1, 0, tx, 0, 1, ty }); // 平移矩阵
            var translationMat = new Mat();
            Cv2.WarpAffine(mat, translationMat, translation, mat.Size(), InterpolationFlags.Linear, BorderTypes.Default, new Scalar(0, 0, 0)); // 平移图像

            //翻转图片
            var flipMat = new Mat();
            var flipCode = FlipMode.X; // 0=上下翻转，1=左右翻转，-1=上下左右翻转
            Cv2.Flip(mat, flipMat, flipCode);

            // 检查图片是否在某种颜色范围内，返回二值图像
            Cv2.InRange(mat, new Scalar(110, 50, 50), new Scalar(130, 255, 255), mat0); // 二值化

            // 保存图像
            string imgPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "output.png");
            dstMat.SaveImage(imgPath);
        }
    
        /// <summary>
        /// 创建一张图片
        /// 纯色、随机雪花
        /// </summary>
        /// <param name="w">宽</param>
        /// <param name="h">高</param>
        /// <param name="isSolidColor">是否是纯色；true=纯色,false=随机雪花</param>
        /// <param name="scalar">纯色时的颜色；Scalar.White 或Scalar.FromVec3b(new Vec3b() { Item0=0,Item1=0,Item2=0})</param>
        /// <returns></returns>
        private Mat CreateImg(int width, int height, bool isSolidColor, Scalar scalar)
        {
            if (isSolidColor)
            {
                Mat mat = new Mat(width, height, MatType.CV_8UC1, scalar);  // 
                return mat;
            }
            else
            {
                Mat mat = new Mat(width, height, MatType.CV_8UC1);  // 

                Random random = new Random();
                for (int i = 0; i < height; i++)
                {
                    for (int j = 0; j < width; j++)
                    {
                        byte blue = (byte)(random.NextDouble() * 256);
                        byte green = (byte)(random.NextDouble() * 256);
                        byte red = (byte)(random.NextDouble() * 256);

                        Vec3b color = new Vec3b(blue, green, red);

                        mat.At<Vec3b>(i, j) = color;
                    }
                }
                return mat;
            }
        }

        /// <summary>
        /// 任意角度旋转图片
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="input">旋转矩阵;</param>
        /// <param name="dsize">输出图片大小</param>
        /// <param name="flags">像素插值方式</param>
        /// <param name="borderMode">背景填充默认为常量</param>
        /// <param name="borderValue">填充颜色默认为黑色</param>
        /// <returns></returns>
        private Mat WarpAffine(Mat mat, InputArray input, Size dsize,
            InterpolationFlags flags = InterpolationFlags.Linear, BorderTypes borderMode = BorderTypes.Constant, Scalar? borderValue = null)
        {
            Mat dst = new Mat();
            Cv2.WarpAffine(mat, dst, input, dsize, flags, borderMode, borderValue);  // dsize=mat.Size()
            return dst;
        }

        /// <summary>
        /// 透视图片
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="srcPoints">源图像中的 三角形顶点坐标 Point2f[] srcPoints = new Point2f[]{ new Point2f(0,0), new Point2f(mat.Cols,0), new Point2f(0,mat.Rows)};</param>
        /// <param name="dstPoints">目标图像中的 三角形顶点坐标 Point2f[] dstPoints = new Point2f[]{ new Point2f(mat.Cols *0.00f,mat.Rows*0.33f), new Point2f(mat.Cols *0.85f,mat.Rows*0.25f), new Point2f(mat.Cols *0.15f,mat.Rows*0.70f)};</param>
        /// <returns></returns>
        private Mat GetAffineTransform(Mat mat, Point2f[] srcPoints, Point2f[] dstPoints)
        {
            Mat dst = new Mat();

            //
            Mat input = Cv2.GetAffineTransform(srcPoints, dstPoints);
            Cv2.WarpAffine(mat, dst, input, mat.Size());  // dsize=mat.Size()
            return dst;
        }

        /// <summary>
        /// 添加边框
        /// </summary>
        /// <param name="srcMat">图片</param>
        /// <param name="top">顶部</param>
        /// <param name="bottom">底部</param>
        /// <param name="left">左部</param>
        /// <param name="right">右部</param>
        /// <param name="borderType">边界类型;Constant纯色，Default扩散</param>
        /// <param name="value">边界颜色</param>
        /// <returns></returns>
        private Mat CopyMakeBorder(Mat srcMat, int top, int bottom, int left, int right, BorderTypes borderType, Scalar? value = null)
        {
            Mat dstMat = new Mat();
            Cv2.CopyMakeBorder(srcMat, dstMat, top, bottom, left, right, borderType, value);
            return dstMat;
        }

        /// <summary>
        /// 计算图片的直方图 - 多通道合并
        /// CalcHist(srcMat, [0, 1, 2], null,1, [256], [new Rangef(0, 256)])
        /// </summary>
        /// <param name="mat"></param>
        /// <param name="channels">需要统计的通道索引；灰度图就是[0],BGR就是[0][1][2]; new int[3] { 0, 1, 2 }</param>
        /// <param name="mask">操作掩码;用于统计出统计直方图的数据元素个数</param>
        /// <param name="dims">需要收集的数据数量；直方图一般只需要收集灰度级一种，所以一般为1</param>
        /// <param name="histSize">存放每个直方图尺寸的数组,一般为256; new int[] { 256 }</param>
        /// <param name="ranges">像素值范围常为[0 256]; new Rangef[] { new Rangef(0, 256) }</param>
        /// <param name="uniform">指示直方图是否均匀；默认为true</param>
        /// <param name="accumulate">累计标识符；默认为false</param>
        /// <returns>输出的图片直方图</returns>
        private Mat CalcHist(Mat mat, int[] channels, InputArray? mask, int dims, int[] histSize, Rangef[] ranges, bool uniform = true, bool accumulate = false)
        {
            Mat[] mats = Cv2.Split(mat); // 分离通道
            Mat hist = new Mat();
            // 计算直方图
            Cv2.CalcHist(mats, channels, mask, hist, dims, histSize, ranges, uniform, accumulate);
            // 归一化
            Cv2.Normalize(hist, hist, 0, 256, NormTypes.MinMax);

            // 获取最小值和最大值
            Cv2.MinMaxLoc(hist, out double minVal, out double maxVal); 
            int hist_h = 512; // 直方图高度
            int hist_w = 512; // 直方图宽度
            hist = hist * (maxVal != 0 ? hist_h / maxVal : 0.0);

            // 创建一张空白图像
            Mat histImage = new Mat(hist_h, hist_w, MatType.CV_8UC3, Scalar.Gray);
            int binW = (int)((double)hist_w / histSize[0]);
            for (int i = 0; i < histSize[0]; i++)
            {
                // 绘制直方图
                histImage.Rectangle(
                    new Point(i * binW, histImage.Rows - (int)hist.Get<float>(i)),
                    new Point((i + 1) * binW, histImage.Rows),
                    new Scalar(255, 255, 255),
                    -1);
            }

            return hist;
        }

        /// <summary>
        /// 计算BGR图片的三色直方图 - BGR三通道拆分
        /// CalcHist_BGR(srcMat, null)
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="mask">操作掩码;用于统计出统计直方图的数据元素个数</param>
        /// <param name="uniform">指示直方图是否均匀；默认为true</param>
        /// <param name="accumulate">累计标识符；默认为false</param>
        /// <returns>输出的图片直方图</returns>
        private void CalcHist_BGR(Mat mat, InputArray? mask, bool uniform = true, bool accumulate = false)
        {
            Mat[] mats = Cv2.Split(mat); // 分离通道
            Mat[] histB = [mats[0]];
            Mat[] histG = [mats[1]];
            Mat[] histR = [mats[2]];
            Mat[] hists = [new Mat(), new Mat(), new Mat()];

            // 直方图尺寸
            int histSize = 256; 
            int[] channels = new int[1] { 0 };
            Cv2.CalcHist(histB, channels, mask, hists[0], 1, [histSize], [new Rangef(0, 256)], uniform, accumulate);
            Cv2.CalcHist(histG, channels, mask, hists[1], 1, [histSize], [new Rangef(0, 256)], uniform, accumulate);
            Cv2.CalcHist(histR, channels, mask, hists[2], 1, [histSize], [new Rangef(0, 256)], uniform, accumulate);
            // 归一化
            Cv2.Normalize(hists[0], hists[0], 0, 256, NormTypes.MinMax);
            Cv2.Normalize(hists[1], hists[1], 0, 256, NormTypes.MinMax);
            Cv2.Normalize(hists[2], hists[2], 0, 256, NormTypes.MinMax);

            // 获取最小值和最大值
            Cv2.MinMaxLoc(hists[0], out double minVal0, out double maxVal0);
            Cv2.MinMaxLoc(hists[1], out double minVal1, out double maxVal1);
            Cv2.MinMaxLoc(hists[2], out double minVal2, out double maxVal2);

            double minVal = Math.Min(minVal0, Math.Min(minVal1, minVal2));
            double maxVal = Math.Max(maxVal0, Math.Max(maxVal1, maxVal2));
            int hist_h = 512; // 直方图高度
            int hist_w = 512; // 直方图宽度
            hists[0] = hists[0] * (maxVal != 0 ? hist_h / maxVal : 0.0);
            hists[1] = hists[1] * (maxVal != 0 ? hist_h / maxVal : 0.0);
            hists[2] = hists[2] * (maxVal != 0 ? hist_h / maxVal : 0.0);

            // 创建一张空白图像
            Mat histImage = new Mat(hist_h, hist_w, MatType.CV_8UC3, Scalar.Gray);
            int binW = (int)((double)hist_w / histSize);
            for (int i = 0; i < histSize; i++)
            {
                // 绘制直方图
                histImage.Rectangle(
                    new Point(i * binW, histImage.Rows - (int)hists[0].Get<float>(i)),
                    new Point((i + 1) * binW, histImage.Rows),
                    new Scalar(255, 0, 0),
                    -1);
                histImage.Rectangle(
                    new Point(i * binW, histImage.Rows - (int)hists[1].Get<float>(i)),
                    new Point((i + 1) * binW, histImage.Rows),
                    new Scalar(0, 255, 0),
                    -1);
                histImage.Rectangle(
                    new Point(i * binW, histImage.Rows - (int)hists[2].Get<float>(i)),
                    new Point((i + 1) * binW, histImage.Rows),
                    new Scalar(0, 0, 255),
                    -1);
            }

        }

        /// <summary>
        /// 绘制直线
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="startPoint">线段的第一个点</param>
        /// <param name="endPoint">线段的第二个点</param>
        /// <param name="color">线条颜色</param>
        /// <param name="thickness">线条宽度</param>
        /// <param name="lineType">线条类型</param>
        /// <param name="shift">偏移量</param>
        /// <returns></returns>
        private Mat DrawLine(Mat mat, Point startPoint, Point endPoint, Scalar color, int thickness = 1, LineTypes lineType = LineTypes.Link8, int shift = 0)
        {
            Cv2.Line(mat, startPoint, endPoint, color, thickness, lineType, shift);
            return mat;
        }

        /// <summary>
        /// 绘制椭圆
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="center">圆点</param>
        /// <param name="axes">半径长度；new Size(A1,A2)</param>
        /// <param name="angle">椭圆偏移角度;>0顺时针,<0逆时针 </param>
        /// <param name="startAngle">椭圆起始角度;>0 </param>
        /// <param name="endAngle">椭圆终止角度;<360 </param>
        /// <param name="color">椭圆线条颜色</param>
        /// <param name="thickness">椭圆线条宽度；负数代表填充</param>
        /// <param name="lineType">椭圆线条类型</param>
        /// <param name="shift">偏移量</param>
        /// <returns></returns>
        private Mat DrawEllipse(Mat mat, Point center, Size axes, double angle, double startAngle, double endAngle, Scalar color, int thickness = 1, LineTypes lineType = LineTypes.Link8, int shift = 0)
        {
            Cv2.Ellipse(mat, center, axes, angle, startAngle, endAngle, color, thickness, lineType, shift);
            return mat;
        }

        /// <summary>
        /// 绘制圆
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="center">圆心坐标</param>
        /// <param name="radius">圆半径</param>
        /// <param name="color">圆线条颜色</param>
        /// <param name="thickness">圆线条宽度；负数代表填充</param>
        /// <param name="lineType">圆线条类型</param>
        /// <param name="shift">偏移量</param>
        /// <returns></returns>
        private Mat DrawCircle(Mat mat, Point center, int radius, Scalar color, int thickness = 1, LineTypes lineType = LineTypes.Link8, int shift = 0)
        {
            Cv2.Circle(mat, center, radius, color, thickness, lineType, shift);
            return mat;
        }

        /// <summary>
        /// 绘制矩形
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="pt1">顶点1</param>
        /// <param name="pt2">顶点1相对应的顶点2</param>
        /// <param name="color">矩形线条颜色</param>
        /// <param name="thickness">矩形线条宽度；负数代表填充</param>
        /// <param name="lineType">矩形线条类型</param>
        /// <param name="shift">偏移量</param>
        /// <returns></returns>
        private Mat DrawRectangle(Mat mat, Point pt1, Point pt2, Scalar color, int thickness = 1, LineTypes lineType = LineTypes.Link8, int shift = 0)
        {
            Cv2.Rectangle(mat, pt1, pt2, color, thickness, lineType, shift);
            return mat;
        }

        /// <summary>
        /// 绘制多边形
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="pts">点位坐标</param>
        /// <param name="isClosed">是否闭合线条</param>
        /// <param name="color">多边形线条颜色</param>
        /// <param name="thickness">多边形线条宽度；负数代表填充</param>
        /// <param name="lineType">多边形线条类型</param>
        /// <param name="shift">偏移量</param>
        /// <returns></returns>
        private Mat DrawPolylines(Mat mat, IEnumerable<IEnumerable<Point>> pts, bool isClosed, Scalar color, int thickness = 1, LineTypes lineType = LineTypes.Link8, int shift = 0)
        {
            Cv2.Polylines(mat, pts, isClosed, color, thickness, lineType, shift);
            return mat;
        }

        /// <summary>
        /// 绘制文本-不支持中文
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="text">文字</param>
        /// <param name="org">文本左上角的起始坐标</param>
        /// <param name="fontFace">字体大小</param>
        /// <param name="fontScale">字体缩放</param>
        /// <param name="color">字体颜色</param>
        /// <param name="thickness">字体线条宽度</param>
        /// <param name="lineType">字体线条类型</param>
        /// <param name="bottomLeftOrigin">是否左下对齐</param>
        /// <returns></returns>
        private Mat DrawText(Mat mat, string text, OpenCvSharp.Point org, HersheyFonts fontFace, double fontScale, Scalar color, int thickness = 1, LineTypes lineType = LineTypes.Link8, bool bottomLeftOrigin = false)
        {
            Cv2.PutText(mat, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin);
            return mat;
        }

        /// <summary>
        /// 绘制文本-支持中文
        /// </summary>
        /// <param name="bitmap">图片</param>
        /// <param name="text">文字内容</param>
        /// <param name="font">字体信息;new Font("宋体", 8)</param>
        /// <param name="smoothingMode">字体平滑模式;SmoothingMode.AntiAlias</param>
        /// <param name="org">位置;new System.Drawing.Point(10, 560)</param>
        /// <param name="brush">字体颜色;System.Drawing.Brushes.White</param>
        /// <returns></returns>
        private System.Drawing.Bitmap DrawText_Utf8(System.Drawing.Bitmap bitmap, string text, System.Drawing.Font font, SmoothingMode smoothingMode, System.Drawing.Point org, System.Drawing.Brush brush)
        {
            using (var graphics = System.Drawing.Graphics.FromImage(bitmap))
            {
                graphics.SmoothingMode = smoothingMode;
                graphics.DrawString(text, font, brush, org);
            }
            return bitmap;
        }

        /// <summary>
        /// 霍夫变换检测直线
        /// </summary>
        /// <param name="mat"></param>
        /// <returns></returns>
        private (Mat, LineSegmentPoint[]) LineDetector_HoughLines(Mat mat)
        {
            var dstMat = new Mat();
            mat.CopyTo(dstMat);

            // 转成灰度图
            var grayMat = new Mat();
            Cv2.CvtColor(mat, grayMat, ColorConversionCodes.BGR2GRAY);

            // 膨胀1次，图形添加轮廓
            var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
            Cv2.Dilate(grayMat, grayMat, kernel, new OpenCvSharp.Point(-1, -1), 1);

            // 边缘检测
            Cv2.Canny(grayMat, grayMat, 50, 200, 3);

            // 霍夫变换检测直线，绘制检测到的直线
            var lines = Cv2.HoughLinesP(grayMat, 1, Math.PI / 180, 50, 50, 10);
            foreach (var line in lines)
            {
                Cv2.Line(dstMat, line.P1, line.P2, Scalar.Red, 2, LineTypes.AntiAlias);
            }

            return (dstMat, lines);
        }

        /// <summary>
        /// 检测圆（霍夫圆变换）
        /// 需要优化
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="resultMat">结果图片</param>
        /// <returns>圆数据</returns>
        private (Mat, CircleSegment[]) CircleDetector_HoughCircles(Mat mat)
        {
            var dstMat = new Mat();
            mat.CopyTo(dstMat);

            // 转化为灰度图
            Mat gray = new Mat();
            Cv2.CvtColor(mat, gray, ColorConversionCodes.BGR2GRAY);

            // 膨胀1次（图型添加轮廓）
            var kenal3 = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(1, 1));
            Cv2.Dilate(gray, gray, kenal3);
            // 膨胀Cv2.Dilate 与 腐蚀Cv2.Dilate

            // 边缘检测（Canny算子）
            Cv2.Canny(gray, gray, 50, 150);

            // 检测圆（霍夫圆变换）
            //# - gray: 待检测的灰度图
            //# - HoughModes_Gradient：检测的方法，霍夫梯度
            //# - 1：检测的圆与原始图像具有相同的大小，dp=2,检测的圆是原始图像的一半
            //# - 20：检测到的圆的中心的最小距离（如果参数为太小，除了一个真实的圆外，还可能会错误地检测到多个相邻圆。如果太大，可能会漏掉一些圆。）
            //# - param1：在#HOUGHŠu梯度的情况下，它是较高的. 两个阈值传递到Canny边缘检测器（较低的一个小两倍）。
            //# - param2：在#HOUGHŠu梯度的情况下，它是检测阶段圆心的累加器阈值。它越小，就越可能检测到假圆；
            //# - minRadius：最小圆半径，也可能会检测到假圆
            //# - maxRadius：最大圆半径，如果<=0，则使用最大图像尺寸。如果<0，则返回没有找到半径的中心。
            CircleSegment[] circles = Cv2.HoughCircles(gray, HoughModes.Gradient, 1, 80, 70, 30, 10, 60);
            if (circles.Count() > 0)
            {
                for (int i = 0; i < circles.Count(); i++)
                {
                    // 画圆
                    Cv2.Circle(dstMat,
                        Convert.ToInt32(circles[i].Center.X), Convert.ToInt32(circles[i].Center.Y),
                        Convert.ToInt32(circles[i].Radius),
                        new Scalar(0, 0, 255), 2, LineTypes.AntiAlias);
                    // 画圆心
                    Cv2.Circle(dstMat,
                        Convert.ToInt32(circles[i].Center.X), Convert.ToInt32(circles[i].Center.Y),
                        3, new Scalar(0, 0, 255), 2, LineTypes.AntiAlias);
                }
            }

            return (dstMat, circles);
        }

        /// <summary>
        /// 检测简易斑点并标注
        /// </summary>
        /// <param name="mat">图片</param>
        /// <param name="resultMat">结果图片</param>
        /// <returns>斑点中心点数据</returns>
        private (Mat, KeyPoint[]) SimpleblobDetector(Mat mat)
        {
            // 转化为灰度图
            Mat gray = new Mat();
            Cv2.CvtColor(mat, gray, ColorConversionCodes.BGR2GRAY);

            // 创建SimpleBlobDetector并设置参数
            SimpleBlobDetector.Params parameters = new SimpleBlobDetector.Params();
            // parameters.BlobColor：斑点的亮度值，取值为0或255，默认为0，表示只检测黑色斑点。
            parameters.FilterByArea = true;  // 是否根据斑点的面积进行过滤，默认为true
            parameters.MinArea = 30;        // 最小的斑点面积，默认为25
            parameters.MaxArea = 6000;      // 最大的斑点面积，默认为5000

            // 创建SimpleBlobDetector
            SimpleBlobDetector detector = SimpleBlobDetector.Create(parameters);

            // 检测斑点
            KeyPoint[] keypoints = detector.Detect(gray);

            // 在图像上绘制斑点
            var dstMat = new Mat();
            Cv2.DrawKeypoints(mat, keypoints, dstMat, Scalar.All(-1));
            return (dstMat, keypoints);
        }


    }

}
